#include "src/scene/object.h"

#include <iostream>
#include <GL/gl.h>
#include <cmath>

#include "src/math/vector.h"

#include "src/defines/rayState.h"
#include "src/defines/functions.h"

#include "src/scene/nearestInfo.h"


Object::Object(int _id, int _type, bool _primitive)
    : id(_id), type(_type), primitive(_primitive), selected(false), parent(NULL)
{
//    setBoundingBox();
}

Object::Object(const Material &_m, int _id, int _type, bool _primitive)
    : id(_id), type(_type), primitive(_primitive), selected(false), material(_m), parent(NULL)
{
//    setBoundingBox();
}

Object::Object(const Material &_m, const Vector3D &_c, int _id, int _type, bool _primitive)
    : id(_id), type(_type), primitive(_primitive), selected(false), material(_m), center(_c), parent(NULL)
{
    transformation.addTranslation(center);
//    setBoundingBox();
}

Object::Object(const Material &_m, const Vector3D &_c, int _id, int _type, bool _primitive, const Matrix &_matrix, const Matrix &_matrixI)
    : id(_id), type(_type), primitive(_primitive), selected(false), material(_m), center(_c), parent(NULL)
{
    transformation.setMatrices(_matrix, _matrixI);
//    setBoundingBox();
}

Object::Object(const Material &_m, const Vector3D &_c, int _id, int _type, bool _primitive, const Transformation &_trans)
    : id(_id), type(_type), primitive(_primitive), selected(false), material(_m), center(_c), transformation(_trans), parent(NULL)
{
//    setBoundingBox();
}

Object::~Object(){
}

int Object::getId() const{
    return id;
}

void Object::setId(int _id){
    id = _id;
}

const Material &Object::getMaterial() const{
    return material;
}

void Object::setMaterial(const Material &_m){
    material = _m;
}

const Vector3D &Object::getCenter() const{
    return center;
}

void Object::setCenter(const Vector3D &_c){
    Vector3D t = _c - center;
    transformation.addTranslation(t);
    center = _c;
    setBoundingBox();
//    relativePosition = relativePosition + (t);
}
//
//void Object::setRelativePosition(const Vector3D &_v){
//    relativePosition = _v;
//}

bool Object::isSelected() const{
    return selected;
}

void Object::select(bool _b){
    selected = _b;
}

bool Object::isPrimitive() const{
    return primitive;
}

int Object::getType() const{
    return type;
}

bool Object::needCaustic() const{
    if(material.kt != 0.0 || material.ks != 0.0) return true;

    return false;
}

void Object::translate(const Vector3D &_t){
    transformation.addTranslation(_t);

    center = center + _t;
    setBoundingBox();
}

void Object::rotate(double _tetha, const Vector3D &_axis, const Vector3D &_relativePosition, bool _useRelativePos){
    Matrix m;
    if(_useRelativePos) m = transformation.addRotation(Functions::toRad(_tetha), _axis.normalize(), center, center - _relativePosition);
    else m = transformation.addRotation(Functions::toRad(_tetha), _axis.normalize(), center, center - center);

    center = (m * center.toHomogeneousCoordinates()).fromHomogeneousCoordinates();
    setBoundingBox();

}

void Object::scale(const Vector3D &_s, const Vector3D &_relativePosition, bool _useRelativePos){
    Matrix m;
    if(_useRelativePos) m = transformation.addScale(_s, center, center - _relativePosition);
    else m = transformation.addScale(_s, center, center - center);

    center = (m * center.toHomogeneousCoordinates()).fromHomogeneousCoordinates();
    setBoundingBox();
}

void Object::boundingBoxWithTransformation(const Vector3D &_min, const Vector3D &_max)
{
    Matrix m = transformation.getLocalToWorld();
    Vector3D m1(m.toVector(0));
    Vector3D m2(m.toVector(1));
    Vector3D m3(m.toVector(2));
    Vector3D m4(m.toVector(3));

    Vector3D xa = m1 * _min.x;
    Vector3D xb = m1 * _max.x;

    Vector3D ya = m2 * _min.y;
    Vector3D yb = m2 * _max.y;

    Vector3D za = m3 * _min.z;
    Vector3D zb = m3 * _max.z;


    boundingBox = HBBBox(min(xa, xb) + min(ya, yb) + min(za, zb) + m4,
                          max(xa, xb) + max(ya, yb) + max(za, zb) + m4);

    if(parent) parent->setBoundingBox();
}

Material Object::chooseMaterial(bool _wireframe, bool _useParentColor, const Material &_parentColor) const{
    Material result;

    if(_useParentColor) result = _parentColor;
    else result = material;

    if(_wireframe && selected){
        double mean[4];
        for(int i = 0; i < 3; i++){
            mean[i] = result.ambient[i];
            mean[i] += result.diffuse[i];
            mean[i] += result.specular[i];
            mean[i] = mean[i] / 3.0;
        }
        if( (mean[0] >= 0.7) && (mean[1] <= 0.3) && (mean[2] <= 0.3) )
        {
            result.ambient.red = result.ambient.green = result.ambient.blue = result.diffuse.red = result.diffuse.green = result.diffuse.blue = result.specular.red = result.specular.green = result.specular.blue = 1.0;
        }
        else
        {
            result.ambient.red = result.diffuse.red = result.specular.red = 1.0;
            result.ambient.green = result.ambient.blue = result.diffuse.green = result.diffuse.blue = result.specular.green = result.specular.blue = 0.0;
        }

    }

    return result;
}

Vector3D Object::normal(const Vector3D &_p) const{
    std::cout <<"Usando a normal geral." <<std::endl;
    return _p;
}

bool Object::hit(const Ray &_ray, NearestInfo *_nearest)
{
    std::vector<Trio> pointsOfObject = intersection(_ray);

    if(_nearest){
        _nearest->object = NULL;

        //!procurando a inteterseção mais proxima
        for(unsigned int j = 0; j < pointsOfObject.size(); j++){
            if(_ray.isInsideOf() == this)//se o raio tiver deentro desse objeto tem que pegar só os fora
            {
                if(pointsOfObject.at(j).type == OUT){
                    double t = (pointsOfObject.at(j).point - _ray.getOrigin()).length_2();
                    if(t < INFINITY){
                        _nearest->setAll(this, t, pointsOfObject.at(j).point, -pointsOfObject.at(j).normal);
                        return true;
                    }
                    break;
                }
            }
            else {
                if(pointsOfObject.at(j).type != OUT){
                    double t = (pointsOfObject.at(j).point - _ray.getOrigin()).length_2();
                    if(t < INFINITY){
                        _nearest->setAll(this, t, pointsOfObject.at(j).point, pointsOfObject.at(j).normal);
                        return true;
                    }
                    break;
                }
            }
        }
    }
    else
    {
        //!procurando a inteterseção mais proxima
        for(unsigned int j = 0; j < pointsOfObject.size(); j++){
            if(_ray.isInsideOf() == this)//se o raio tiver deentro desse objeto tem que pegar só os fora
            {
                if(pointsOfObject.at(j).type == OUT){
                    double t = (pointsOfObject.at(j).point - _ray.getOrigin()).length_2();
                    if(t < INFINITY){
                        return true;
                    }
                    break;
                }
            }
            else
            {
                if(pointsOfObject.at(j).type != OUT){
                    double t = (pointsOfObject.at(j).point - _ray.getOrigin()).length_2();
                    if(t < INFINITY){
                        return true;
                    }
                    break;
                }
            }
        }
    }
    return false;
}

bool Object::drawBegin(bool _wireframe, bool _useParentColor, const Material &_parentColor, double *_minTexU, double *_maxTexU, double *_minTexV, double *_maxTexV) const
{

    bool useTexture = false;
    if(_wireframe){
        glDisable(GL_LIGHTING);

        glEnable(GL_COLOR_MATERIAL);

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDisable(GL_CULL_FACE);

        Material color_ = chooseMaterial(_wireframe, _useParentColor, _parentColor);
        Color color__ = ((color_.ambient * color_.ka) + (color_.diffuse*color_.kd) + (color_.specular * color_.ks))/3.0;

        double alpha = 1 - color_.kt;
//        if(alpha > 1) alpha = 1;
//        else (alpha < 0) alpha = 0;
        glColor4f(color__.red, color__.green, color__.blue, alpha);
    }
    else
    {
        //glDepthMask(GL_FALSE);

        Material color_ = chooseMaterial(_wireframe, _useParentColor, _parentColor);
        GLfloat color__[4];
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color_.getAmbientGL(&color__[0]));
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color_.getDiffuseGL(&color__[0]));
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color_.getSpecularGL(&color__[0]));
        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, color_.specularPower);

        useTexture = color_.useTexture();
        if(useTexture){
            glEnable(GL_TEXTURE_2D);
            #if !USE_CONSOLE
            color_.texture->useTexture();
            #endif //!USE_CONSOLE
            color_.texture->getMinMax(_minTexU, _maxTexU, _minTexV, _maxTexV);
        }
    }

	glPushMatrix();
	glMultMatrixd(transformation.getLocalToWorld().toGlMatrix());

    return useTexture;

}

void Object::drawEnd(bool _wireframe, bool _useTexture) const
{
	glPopMatrix();

    if(_wireframe){
//        glLineWidth(1);

        glEnable(GL_LIGHTING);
        glDisable(GL_COLOR_MATERIAL);

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glEnable(GL_CULL_FACE);
    }
    else
    {
        //glDepthMask(GL_TRUE);
        if(_useTexture)
            glDisable(GL_TEXTURE_2D);
    }

    if(selected)
        boundingBox.draw(Color(1, 0, 0));
}
